package com.gitee.loyo.config;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gitee.loyo.Request;
import com.gitee.loyo.config.properties.ChargeProperties;
import com.gitee.loyo.config.properties.ChargeNotifyProperty;
import com.gitee.loyo.err.CoordinationFailureException;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;

@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class ChargeRequestBodyAdvice extends RequestBodyAdviceAdapter {
    final ObjectMapper objectMapper;
    final AntPathMatcher pathMatcher = new AntPathMatcher();
    final ChargeProperties chargeProperties;

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return methodParameter.getParameterType() == Request.class;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        //需要获取请求的URL
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURL().toString();
        log.info("请求地址：{}", url);
        String pathPattern = chargeProperties.getContextPath();
        String contextPathMatch = chargeProperties.getContextPathMatch();
        String operatorKey = null;
        if (pathMatcher.match(pathPattern, url)) {
            operatorKey = pathMatcher.extractUriTemplateVariables(pathPattern, url).get(contextPathMatch);
        }
        if (!StringUtils.hasText(operatorKey)) {
            throw new CoordinationFailureException("未匹配到operator对接的URL");
        }
        attributes.setAttribute(contextPathMatch, operatorKey, RequestAttributes.SCOPE_REQUEST);

        return new ChargeHttpInputMessage(inputMessage,
                (Class<?>) ((ParameterizedTypeImpl) targetType).getActualTypeArguments()[0], operatorKey);
    }

    public class ChargeHttpInputMessage implements HttpInputMessage {
        private HttpHeaders headers;
        private InputStream body;

        @SneakyThrows
        public ChargeHttpInputMessage(HttpInputMessage inputMessage, Class<?> type, String operatorKey) {
            this.headers = inputMessage.getHeaders();
            byte[] body = ByteStreams.toByteArray(inputMessage.getBody());
            log.info("receive: \n{}", new String(body, "UTF-8"));
            Request<String> request = objectMapper.readValue(body, new TypeReference<Request<String>>() {
            });

            ChargeNotifyProperty notifyProperty = chargeProperties.getNotifyProperty(operatorKey);

            log.info("sign: {}", request.checkSign(notifyProperty.getSigSecret()));
//            if(!request.checkSign(notifyProperty.getSigSecret())){
//                throw new SignVerifyFailureException(new String(body, "UTF-8"));
//            }
            Request<Object> newRequest = request.decrypt(
                    notifyProperty.getDataSecretIV(),
                    notifyProperty.getDataSecret(),
                    s -> objectMapper.readValue(s, type)
            );
            body = objectMapper.writeValueAsBytes(newRequest);
            this.body = ByteSource.wrap(body).openStream();
        }

        @Override
        public InputStream getBody() {
            return body;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }
}
